#[derive(Debug, PartialEq)]
pub struct Stylesheet {
  pub rules: Vec<Rule>,
}

#[derive(Debug, PartialEq)]
pub struct Rule {
  pub selectors: Vec<Selector>,
  pub declarations: Vec<Declaration>,
}

#[derive(Debug, PartialEq)]
pub enum Selector {
  Simple(SimpleSelector),
}

#[derive(Debug, PartialEq)]
pub struct SimpleSelector {
  pub tag_name: Option<String>,
  pub id: Option<String>,
  pub class: Vec<String>,
}

#[derive(Debug, PartialEq)]
pub struct Declaration {
  pub name: String,
  pub value: Value,
}

#[derive(Debug, PartialEq, Clone)]
pub enum Value {
  Keyword(String),
  Length(f32, Unit),
  ColorValue(Color),
}

#[derive(Debug, PartialEq, Clone)]
pub enum Unit {
  Px,
}

#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Color {
  pub r: u8,
  pub g: u8,
  pub b: u8,
  pub a: u8,
}

// impl Copy for Color {}

pub type Specificity = (usize, usize, usize);

impl Selector {
  // http://www.w3.org/TR/selectors/#specificity
  pub fn specificity(&self) -> Specificity {
    let Selector::Simple(simple) = self;
    let a = simple.id.iter().count();
    let b = simple.class.len();
    let c = simple.tag_name.iter().count();
    (a, b, c)
  }
}

impl Value {
  pub fn to_px(&self) -> f32 {
    match *self {
      Value::Length(f, Unit::Px) => f,
      _ => 0.0,
    }
  }
}

pub fn parse(source: String) -> Stylesheet {
  let mut parser = Parser {
    pos: 0,
    input: source,
  };
  Stylesheet {
    rules: parser.parse_rules(),
  }
}

struct Parser {
  pos: usize,
  input: String,
}

impl Parser {
  fn parse_rules(&mut self) -> Vec<Rule> {
    let mut rules = Vec::new();
    loop {
      self.consume_whitespace();
      if self.eof() {
        break;
      }
      rules.push(self.parse_rule());
    }
    rules
  }

  fn parse_rule(&mut self) -> Rule {
    Rule {
      selectors: self.parse_selectors(),
      declarations: self.parse_declarations(),
    }
  }

  fn parse_selectors(&mut self) -> Vec<Selector> {
    let mut selectors = Vec::new();
    loop {
      selectors.push(Selector::Simple(self.parse_simple_selector()));
      self.consume_whitespace();
      match self.next_char() {
        ',' => {
          self.consume_char();
          self.consume_whitespace();
        }
        '{' => break,
        c => panic!("Unexpected character {} in selector list", c),
      }
    }
    selectors.sort_by(|a, b| b.specificity().cmp(&a.specificity()));
    selectors
  }

  fn parse_simple_selector(&mut self) -> SimpleSelector {
    let mut selector = SimpleSelector {
      tag_name: None,
      id: None,
      class: Vec::new(),
    };
    while !self.eof() {
      match self.next_char() {
        '#' => {
          self.consume_char();
          selector.id = Some(self.parse_identifier());
        }
        '.' => {
          self.consume_char();
          selector.class.push(self.parse_identifier());
        }
        '*' => {
          self.consume_char();
        }
        c if valid_identifier_char(c) => {
          selector.tag_name = Some(self.parse_identifier());
        }
        _ => break,
      }
    }
    selector
  }

  fn parse_declarations(&mut self) -> Vec<Declaration> {
    assert_eq!(self.consume_char(), '{');
    let mut declarations = Vec::new();
    loop {
      self.consume_whitespace();
      if self.next_char() == '}' {
        self.consume_char();
        break;
      }
      declarations.push(self.parse_declaration());
    }
    declarations
  }

  fn parse_declaration(&mut self) -> Declaration {
    let name = self.parse_identifier();
    self.consume_whitespace();
    assert_eq!(self.consume_char(), ':');
    self.consume_whitespace();
    let value = self.parse_value();
    self.consume_whitespace();
    assert_eq!(self.consume_char(), ';');
    Declaration { name, value }
  }

  fn parse_value(&mut self) -> Value {
    match self.next_char() {
      '0'..='9' => self.parse_length(),
      '#' => self.parse_color(),
      _ => Value::Keyword(self.parse_identifier()),
    }
  }

  fn parse_length(&mut self) -> Value {
    Value::Length(self.parse_float(), self.parse_unit())
  }

  fn parse_float(&mut self) -> f32 {
    let s = self.consume_while(|c| match c {
      '0'..='9' | '.' => true,
      _ => false,
    });
    s.parse().unwrap()
  }

  fn parse_unit(&mut self) -> Unit {
    match &self.parse_identifier().to_ascii_lowercase()[..] {
      "px" => Unit::Px,
      _ => panic!("Unrecognized unit"),
    }
  }

  fn parse_color(&mut self) -> Value {
    assert_eq!(self.consume_char(), '#');
    Value::ColorValue(Color {
      r: self.parse_hex_pair(),
      g: self.parse_hex_pair(),
      b: self.parse_hex_pair(),
      a: 255,
    })
  }

  fn parse_hex_pair(&mut self) -> u8 {
    let s = &self.input[self.pos..self.pos + 2];
    self.pos += 2;
    u8::from_str_radix(s, 16).unwrap()
  }

  fn consume_whitespace(&mut self) {
    self.consume_while(char::is_whitespace);
  }

  fn consume_while<F>(&mut self, test: F) -> String
  where
    F: Fn(char) -> bool,
  {
    let mut result = String::new();
    while !self.eof() && test(self.next_char()) {
      result.push(self.consume_char());
    }
    result
  }

  fn parse_identifier(&mut self) -> String {
    self.consume_while(valid_identifier_char)
  }

  fn consume_char(&mut self) -> char {
    let mut iter = self.input[self.pos..].char_indices();
    let (_, cur_char) = iter.next().unwrap();
    let (next_pos, _) = iter.next().unwrap_or((1, ' '));
    self.pos += next_pos;
    cur_char
  }

  fn next_char(&self) -> char {
    self.input[self.pos..].chars().next().unwrap()
  }

  fn eof(&self) -> bool {
    self.pos >= self.input.len()
  }
}

fn valid_identifier_char(c: char) -> bool {
  match c {
    'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => true,
    _ => false,
  }
}

#[cfg(test)]
mod test {
  use super::*;

  fn hex_value(s: &str) -> u8 {
    u8::from_str_radix(s, 16).unwrap()
  }

  #[test]
  fn parse_color_test() {
    let mut parser = Parser {
      pos: 0,
      input: "#3c2d5f".to_string(),
    };
    let color_value = parser.parse_color();
    let value_expected = Value::ColorValue(Color {
      r: hex_value("3c"),
      g: hex_value("2d"),
      b: hex_value("5f"),
      a: 255,
    });
    assert_eq!(color_value, value_expected)
  }

  #[test]
  fn parse_unit_test() {
    let mut parser = Parser {
      pos: 0,
      input: "Px".to_string(),
    };
    let unit = parser.parse_unit();
    assert_eq!(unit, Unit::Px);
  }

  #[test]
  fn specificity_test() {
    let selector = Selector::Simple(SimpleSelector {
      tag_name: Some("tag".to_string()),
      id: Some("testid".to_string()),
      class: vec!["classa".to_string(), "classb".to_string()],
    });
    assert_eq!(selector.specificity(), (1, 2, 1));
  }

  #[test]
  fn parse_test() {
    let css = "div, #id, .class.classb {background-color: #ccddee;display:none;}";
    let stylesheet = parse(css.to_string());
    let excepted = Stylesheet {
      rules: vec![Rule {
        selectors: vec![
          Selector::Simple(SimpleSelector {
            tag_name: None,
            id: Some("id".to_string()),
            class: Vec::new(),
          }),
          Selector::Simple(SimpleSelector {
            tag_name: None,
            id: None,
            class: vec!["class".to_string(), "classb".to_string()],
          }),
          Selector::Simple(SimpleSelector {
            tag_name: Some("div".to_string()),
            id: None,
            class: Vec::new(),
          }),
        ],
        declarations: vec![
          Declaration {
            name: "background-color".to_string(),
            value: Value::ColorValue(Color {
              r: hex_value("cc"),
              g: hex_value("dd"),
              b: hex_value("ee"),
              a: 255,
            }),
          },
          Declaration {
            name: "display".to_string(),
            value: Value::Keyword("none".to_string()),
          },
        ],
      }],
    };
    assert_eq!(stylesheet, excepted);
  }
}
